<?php
/**
 * 
 * vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
 * 
 * A very simple helper for building HTML tables.
 *
 * The source code has been heavily modified based on Solar_View_Helper_Form.
 * 
 * This is a fluent class; all method calls except fetch() return
 * $this, which means you can chain method calls for easier readability.
 * 
 * This class handles the creation of a table utilizing only simple tags and 
 * attributes but allow for customization of attributes by supplying an 
 * attribute array which is used for the <table>, <col>, <th>, <td> and 
 * <caption> tags. Some may argue that this is not sufficient since 
 * attributes may also be needed in <thead>, <tbody>, <tfoot> and <tr> tags. 
 * Any suggestions or code contributions are most welcome.
 *
 * {{code: php
 *  $table = $this->table(array('cellpadding' => 1, 'cellspacing' => 1));
 *
 *  $table->addCaption('Address Book');
 *
 *  // Add a row into table head
 *  $table->addHeadRow(
 *      // column specification array
 *      array(
 *          // column 1 only has content
 *          "Name",
 *          // column 2 has content and attribs
 *          array(
 *              'content' => 'Email',
 *              // override the default cell style
 *              'attribs' => array('style' => 'background: #ffcccc;'),
 *              // do not use <th>, meaning <td> is used instead
 *              'use_th' => false,
 *          ),
 *          // column 3 has only content
 *          "Address",
 *      ),
 *      // default attribs for all <th> and <td>
 *      array('style' => 'background: #ffffff;',)
 *  );
 *
 *  // Columns may be added without any attributes too.
 *  $table->addBodyRow(array(
 *      'Alfred E. Oldman',
 *      'oldman_randomshit@gmail.com',
 *      '13A, North Tyndall Ave, Utopia, CA 90028.',
 *  ));
 *
 *  echo $table->fetch();
 * }}
 *
 * Example code above produces the following markup:
 *
 * <table cellpadding="1" cellspacing="1">
 *     <caption>Address Book</caption>
 *     <thead>
 *         <tr>
 *             <th style="background: #ffffff;">Name</th>
 *             <td style="background: #ffcccc;">Email</td>
 *             <th style="background: #ffffff;">Address</th>
 *         </tr>
 *     </thead>
 *     <tbody>
 *         <tr>
 *             <td>Alfred E. Oldman</td>
 *             <td>oldman_randomshit@gmail.com</td>
 *             <td>13A, North Tyndall Ave, Utopia, CA 90028.</td>
 *         </tr>
 *     </tbody>
 * </table>
 * 
 * @category Xdms
 * 
 * @author Paul M. Jones <pmjones@solarphp.com>
 *
 * @author Matthew Weier O'Phinney <mweierophinney@gmail.com>
 *
 * @author Abu Bakar Al-Idrus <alidrus@gmail.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id$
 * 
 */
class Xdms_View_Helper_Table extends Solar_View_Helper
{
    /**
     * 
     * Default configuration values.
     * 
     * @var array
     * 
     */
    protected $_Solar_View_Helper_Table = array(
    );

    /**
     *
     * Internal object for creating Table
     *
     * @var Xdms_Table
     */
    protected $_table;

    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    public function _postConstruct()
    {
        parent::_postConstruct();
        
        // instantiate Xdms_Table
        $this->_table = Solar::factory('Xdms_Table');
    }

    /**
     *
     * Main method interface to Solar_View
     *
     * @param Xdms_Table|array $spec If an Xdms_Table object, does full auto 
     * build and fetch of a table based on the Xdms_Table properties. If an 
     * array, treated as attribute keys and values for the <table> tag
     *
     * @return string|Xdms_View_Helper_Table
     *
     */
    public function table($spec = null) {
        if ($spec instanceof Xdms_Table) {
            // auto-build and fetch from an Xdms_Table object
            $this->auto($spec);
            return $this->fetch();
        } elseif (is_array($spec)) {
            // set attributes from an array
            $this->_table->setAttribs($spec);
            return $this;
        } else {
            // just return self
            return $this;
        }
    }

    /**
     * Add caption to the table.
     *
     * @param array|string $info If an array, then extract the content and 
     * attribs from it. If a string, then use that as the content.
     *
     * @return Xdms_View_Helper_Table
     *
     */
    public function addCaption($info) {
        // setup call variables
        $caption = array();
        $caption['content'] = array_key_exists('content', (array) $info)
            ? $info['content']
            : (string) $info;
        $caption['attribs'] = array_key_exists('attribs', (array) $info)
            ? $info['attribs']
            : array();

        // make the call to Xdms_Table::setCaption
        $this->_table->setCaption($caption['content'], $caption['attribs']);

        // return self
        return $this;
    }

    /**
     *
     * Add a column group specification tag or <colgroup> to the table.
     *
     * @param array $info An array of attributes for this <colgroup>.
     *
     * @return void
     *
     */
    public function addColGroup($info) {
        // make the call to Xdms_Table::setCol
        $this->_table->setColGroup($info);

        // return self
        return $this;
    }

    /**
     *
     * Add a column specification tag or <col> to the table.
     *
     * @param array $info An array of attributes for this <col>.
     *
     * @return void
     *
     */
    public function addCol($info) {
        // make the call to Xdms_Table::setCol
        $this->_table->setCol($info);

        // return self
        return $this;
    }

    /**
     *
     * Add a row to the table head section.
     *
     * @param array $info An array containing the row specification.
     *
     * @return void
     *
     */
    public function addHeadRow($info) {
        // setup call variables
        $row = array();
        $row['cols']    = array_key_exists('cols',    (array) $info) ? $info['cols']    : $info;
        $row['attribs'] = array_key_exists('attribs', (array) $info) ? $info['attribs'] : array();
        $row['use_th']  = array_key_exists('use_th',  (array) $info) ? $info['use_th']  : true;
        $row['row_id']  = array_key_exists('row_id',  (array) $info) ? $info['row_id']  : null;

        // make the call
        $this->_table->setHeadRow($row['cols'], $row['attribs'], $row['use_th'], $row['row_id']);

        // return self
        return $this;
    }

    /**
     *
     * Add a row to the table foot section.
     *
     * @param array $info An array containing the row specification.
     *
     * @return void
     *
     */
    public function addFootRow($info) {
        // setup call variables
        $row = array();
        $row['cols']    = array_key_exists('cols',    (array) $info) ? $info['cols']    : $info;
        $row['attribs'] = array_key_exists('attribs', (array) $info) ? $info['attribs'] : array();
        $row['use_th']  = array_key_exists('use_th',  (array) $info) ? $info['use_th']  : true;
        $row['row_id']  = array_key_exists('row_id',  (array) $info) ? $info['row_id']  : null;

        // make the call
        $this->_table->setFootRow($row['cols'], $row['attribs'], $row['use_th'], $row['row_id']);

        // return self
        return $this;
    }

    /**
     *
     * Add a row to the table body section.
     *
     * @param array $info An array containing the row specification.
     *
     * @return void
     *
     */
    public function addBodyRow($info) {
        // setup call variables
        $row = array();
        $row['cols']    = array_key_exists('cols', $info)    ? $info['cols']    : $info;
        $row['attribs'] = array_key_exists('attribs', $info) ? $info['attribs'] : array();
        $row['use_th']  = array_key_exists('use_th', $info)  ? $info['use_th']  : false;
        $row['row_id']  = array_key_exists('row_id', $info)  ? $info['row_id']  : null;

        // make the call
        $this->_table->setBodyRow($row['cols'], $row['attribs'], $row['use_th'], $row['row_id']);

        // return self
        return $this;
    }

    /**
     *
     * Automatically populates the form from an existing Xdms_Table object.
     * 
     * @param Xdms_Table $spec The Xdms_Table object to be implanted.
     *
     * @return Xdms_View_Helper_Table
     *
     */
    public function auto($spec) {
        if ($spec instanceof Xdms_Table) {
            // Simply assign $this->table to the value of spec
            $this->_table = $spec;
        }

        // return self
        return $this;
    }

    /**
     *
     * Builds and returns the table output.
     *
     * @param int $indent Number of steps to indent at the top level <table> 
     * tag.
     * 
     * @return string
     * 
     * @see The entire set of _build*() methods.
     *
     */
    public function fetch($indent = 0) {
        // stack of output pieces
        $html = array();

        // the opening table tag
        $this->_buildBegin($html, $indent);

        // the table caption
        $this->_buildCaption($html, $indent + 1);

        // the table colgroup
        $this->_buildColGroup($html, $indent + 1);

        // the table col
        $this->_buildCol($html, $indent + 1);

        // the head section
        $this->_buildHead($html, $indent + 1);

        // the foot section
        $this->_buildFoot($html, $indent + 1);

        // the body section
        $this->_buildBody($html, $indent + 1);

        // the closing table tag
        $this->_buildEnd($html, $indent);

        // done, return the output pieces (append \n for consistency)
        return implode("\n", $html)."\n";
    }

    /**
     *
     * Reset the table entirely.
     *
     * @return void
     *
     */
    public function reset() {
        $this->_table->reset();
    }

    /**
     * Returns text indented to a number of levels.
     * 
     * @param int $num The number of levels to indent.
     * 
     * @param string $text The text to indent.
     * 
     * @return string The indented text.
     * 
     */
    protected function _indent($num, $text = null)
    {
        return str_repeat(' ', $num * 4) . $text;
    }

    /**
     *
     * Builds the opening <table> tag for output.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildBegin(&$html, $indent) {
        // get the table array
        $table = $this->_table->getTable();

        // create the <table> tag
        $html[] = $this->_indent($indent, '<table' . $this->_view->attribs($table['attribs']) . '>');
    }

    /**
     *
     * Build the <caption> tag together with contents and attributes.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildCaption(&$html, $indent) {
        $table = $this->_table->getTable();
        $caption = $table['caption'];
        if ($caption) {
            $html[] = $this->_indent(
                $indent,
                '<caption' . $this->_view->attribs($caption['attribs']) . '>'
                .$this->_view->getTextRaw($caption['content'])
                .'</caption>'
            );
        }
    }

    /**
     *
     * Build the <colgroup> tag together with attributes.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildColGroup(&$html, $indent) {
        $table = $this->_table->getTable();

        // return if there is col list is empty
        if (! $table['colgroup']) {
            return;
        }

        //
        $cols = $table['colgroup'];
        foreach($cols as $col) {
            $html[] = $this->_indent($indent, '<colgroup' . $this->_view->attribs($col['attribs']) . '/>');
        }
    }

    /**
     *
     * Build the <col> tag together with attributes.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildCol(&$html, $indent) {
        $table = $this->_table->getTable();

        // return if there is col list is empty
        if (! $table['col']) {
            return;
        }

        //
        $cols = $table['col'];
        foreach($cols as $col) {
            $html[] = $this->_indent($indent, '<col' . $this->_view->attribs($col['attribs']) . '/>');
        }
    }

    /**
     *
     * Build the <thead> tag together with children <tr>, grandchildren <td> 
     * and their attributes. At the moment, this does not handle the attributes 
     * in the <tr> or <thead> tags themselves.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildHead(&$html, $indent) {
        $table = $this->_table->getTable();

        if (! $table['head']) {
            return;
        }

        // opening thead tag
        $html[] = $this->_indent($indent, '<thead>');

        //
        $rows = $table['head'];
        foreach($rows as $row) {
            // opening tr tag
            $html[] = $this->_indent($indent + 1, '<tr>');

            foreach($row as $col) {
                // td tags
                $html[] = $this->_indent(
                    $indent + 2,
                    '<' . ($col['use_th'] ? 'th':'td') . $this->_view->attribs($col['attribs']) . '>'
                    . $this->_view->getTextRaw($col['content'])
                    . '</' . ($col['use_th'] ? 'th':'td') . '>'
                );
            }

            // closing tr tag
            $html[] = $this->_indent($indent + 1, '</tr>');
        }

        // closing thead tag
        $html[] = $this->_indent($indent, '</thead>');
    }

    /**
     *
     * Build the <tfoot> tag together with children <tr>, grandchildren <td> 
     * and their attributes. At the moment, this does not handle the attributes 
     * in the <tr> or <tfoot> tags themselves.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildFoot(&$html, $indent) {
        $table = $this->_table->getTable();

        if (! $table['foot']) {
            return;
        }

        // opening tfoot tag
        $html[] = $this->_indent($indent, '<tfoot>');

        //
        $rows = $table['foot'];
        foreach($rows as $row) {
            // opening tr tag
            $html[] = $this->_indent($indent + 1, '<tr>');

            foreach($row as $col) {
                // td tags
                $html[] = $this->_indent(
                    $indent + 2,
                    '<' . ($col['use_th'] ? 'th':'td') . $this->_view->attribs($col['attribs']) . '>'
                    . $this->_view->getTextRaw($col['content'])
                    . '</' . ($col['use_th'] ? 'th':'td') . '>'
                );
            }

            // closing tr tag
            $html[] = $this->_indent($indent + 1, '</tr>');
        }

        // closing tfoot tag
        $html[] = $this->_indent($indent, '</tfoot>');
    }

    /**
     *
     * Build the <tbody> tag together with children <tr>, grandchildren <td> 
     * and their attributes. At the moment, this does not handle the attributes 
     * in the <tr> or <tbody> tags themselves.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildBody(&$html, $indent) {
        $table = $this->_table->getTable();

        if (! $table['body']) {
            return;
        }

        // opening tbody tag
        $html[] = $this->_indent($indent, '<tbody>');

        //
        $rows = $table['body'];
        foreach($rows as $row) {
            // opening tr tag
            $html[] = $this->_indent($indent + 1, '<tr>');

            foreach($row as $col) {
                // td tags
                $html[] = $this->_indent(
                    $indent + 2,
                    '<' . ($col['use_th'] ? 'th':'td') . $this->_view->attribs($col['attribs']) . '>'
                    . $this->_view->getTextRaw($col['content'])
                    . '</' . ($col['use_th'] ? 'th':'td') . '>'
                );
            }

            // closing tr tag
            $html[] = $this->_indent($indent + 1, '</tr>');
        }

        // closing tbody tag
        $html[] = $this->_indent($indent, '</tbody>');
    }

    /**
     *
     * Builds the closing </table> tag for output.
     *
     * @param array &$html A reference to the array of HTML lines for output.
     *
     * @param int $indent Number of steps to indent.
     *
     * @return void
     *
     */
    protected function _buildEnd(&$html, $indent) {
        $html[] = $this->_indent($indent, '</table>');
    }
}
